package com.zys.http.processor;

import com.intellij.navigation.NavigationItem;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.*;
import com.intellij.psi.impl.compiled.ClsEnumConstantImpl;
import com.intellij.psi.impl.source.PsiFieldImpl;
import com.zys.http.constant.HttpEnum;
import com.zys.http.extension.gutter.HttpLineMarkerInfo;
import com.zys.http.tool.ThreadTool;
import com.zys.http.tool.UrlTool;
import com.zys.http.ui.tree.node.MethodNode;
import lombok.SneakyThrows;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author zys
 * @since 2025-01-13
 */
public abstract class AbstractJavaProcessor extends AbstractProcessor {
    // region annotation
    @Override
    public boolean isTargetAnnotation(PsiElement element, String qualifiedName) {
        return element instanceof PsiAnnotation annotation && annotation.hasQualifiedName(qualifiedName);
    }

    @Override
    public @NotNull List<String> annotationValues(PsiElement element, String[] attributeNames) {
        if (!(element instanceof PsiAnnotation annotation)) {
            return DEFAULT_ANNO_VALUE;
        }
        List<PsiAnnotationMemberValue> initializers = new ArrayList<>();
        for (String attributeName : attributeNames) {
            PsiAnnotationMemberValue annoValue = annotation.findAttributeValue(attributeName);
            if (annoValue instanceof PsiArrayInitializerMemberValue arrayAnnoValues) {
                initializers.addAll(List.of(arrayAnnoValues.getInitializers()));
            } else {
                initializers.add(annoValue);
            }
        }
        if (initializers.isEmpty()) {
            return DEFAULT_ANNO_VALUE;
        }
        List<String> list = initializers.stream().map(this::expressionParse).filter(Objects::nonNull).distinct().toList();
        return list.isEmpty() ? DEFAULT_ANNO_VALUE : list;
    }

    // endregion annotation

    // region class

    @Override
    public Set<? extends PsiElement> controllerAnnotations(PsiElement element) {
        if (!(element instanceof PsiClass psiClass)) {
            return Collections.emptySet();
        }
        PsiModifierList modifierList = psiClass.getModifierList();
        if (modifierList == null) {
            return Collections.emptySet();
        }

        Set<PsiElement> res = new HashSet<>(Set.of(modifierList.getAnnotations()));
        PsiClass[] interfaces = psiClass.getInterfaces();
        for (PsiClass anInterface : interfaces) {
            res.addAll(List.of(anInterface.getAnnotations()));
        }
        return res;
    }

    @Override
    public String controllerPackageName(PsiElement element) {
        return Optional.of(element)
                .filter(PsiClass.class::isInstance)
                .map(PsiClass.class::cast)
                .map(PsiClass::getContainingFile)
                .filter(PsiJavaFile.class::isInstance)
                .map(PsiJavaFile.class::cast)
                .map(PsiJavaFile::getPackageName)
                .orElse(null);
    }

    @Override
    public String controllerSwagger(PsiElement element) {
        return element instanceof PsiClass psiClass ?
                getSwagger(psiClass.getModifierList(), List.of(HttpEnum.Swagger.API, HttpEnum.Swagger.TAG)) : "";
    }

    @SneakyThrows
    private String getSwagger(PsiModifierList modifierList, List<HttpEnum.Swagger> swaggers) {
        if (Objects.isNull(modifierList)) {
            return "";
        }
        List<PsiAnnotation> list = ReadAction.nonBlocking(() -> Stream.of(modifierList.getAnnotations())
                .filter(o -> swaggers.stream().map(HttpEnum.Swagger::getClazz).toList().contains(o.getQualifiedName()))
                .toList()).submit(ThreadTool.getExecutor()).get();

        if (list.isEmpty()) {
            return "";
        }
        PsiAnnotation annotation = list.get(0);
        HttpEnum.Swagger operation = swaggers.stream().filter(o -> isTargetAnnotation(annotation, o.getClazz()))
                .findFirst().orElse(null);
        if (Objects.isNull(operation)) {
            return "";
        }
        return annotationValue(annotation, new String[]{operation.getValue()});
    }

    @Override
    public List<? extends PsiElement> controllerMethods(PsiElement element) {
        if (!(element instanceof PsiClass psiClass)) {
            return Collections.emptyList();
        }
        if (psiClass.isAnnotationType() || psiClass.isInterface() || psiClass.isEnum()) {
            return Collections.emptyList();
        }
        return Arrays.stream(psiClass.getMethods())
                .filter(this::isTargetMethod)
                .collect(Collectors.toList());
    }

    // endregion class

    // region method


    @Override
    @SneakyThrows
    public String methodSwagger(PsiElement element) {
        return element instanceof PsiMethod method ? getSwagger(method.getModifierList(), List.of(HttpEnum.Swagger.API_OPERATION, HttpEnum.Swagger.OPERATION)) : "";
    }

    @Override
    public List<MethodNode> methodNodes(PsiElement element, String contextPath) {
        if (!(element instanceof PsiClass psiClass)) {
            return Collections.emptyList();
        }
        if (psiClass.isAnnotationType() || psiClass.isInterface() || psiClass.isEnum()) {
            return Collections.emptyList();
        }
        List<? extends PsiElement> methods = controllerMethods(psiClass);
        List<String> controllerPaths = controllerPaths(psiClass);
        List<MethodNode> res = new ArrayList<>();
        for (String controllerPath : controllerPaths) {
            for (PsiElement method : methods) {
                List<MethodNode> methodNodes = methodNodeCreate(method, controllerPath, contextPath, true);
                if (methodNodes != null && !methodNodes.isEmpty()) {
                    res.addAll(methodNodes);
                }
            }
        }
        return res;
    }

    @Override
    public List<? extends PsiElement> methodParameters(PsiElement element) {
        if (!(element instanceof PsiMethod method)) {
            return Collections.emptyList();
        }
        PsiParameterList parameterList = method.getParameterList();
        if (parameterList.isEmpty()) {
            return Collections.emptyList();
        }
        return List.of(parameterList.getParameters());
    }
    // endregion method

    // region expression

    @Override
    public String expressionParse(PsiElement element) {
        if (element instanceof PsiLiteralExpression expression) {
            return literalExpressionParse(expression);
        } else if (element instanceof PsiReferenceExpression expression) {
            return referenceExpressionParse(expression);
        } else if (element instanceof PsiPolyadicExpression expression) {
            return polyadicExpressionParse(expression);
        }
        return null;
    }

    private String literalExpressionParse(PsiLiteralExpression expression) {
        String text = expression.getText();
        return text.startsWith("\"") && text.endsWith("\"") ? text.substring(1, text.length() - 1) : text;
    }

    private String referenceExpressionParse(PsiReferenceExpression expression) {
        PsiElement resolve = expression.resolve();
        if (resolve == null) {
            return null;
        }
        if (resolve instanceof ClsEnumConstantImpl) {
            return expression.getText();
        }

        return resolve instanceof PsiFieldImpl field ? expressionParse(field.getInitializer()) : null;
    }

    private String polyadicExpressionParse(PsiPolyadicExpression expression) {
        PsiExpression[] expressions = expression.getOperands();
        StringBuilder res = new StringBuilder();
        for (PsiExpression ex : expressions) {
            String s = expressionParse(ex);
            if (s != null) {
                res.append(s);
            }
        }
        return res.toString();
    }

    // endregion expression


    @Override
    public final List<PostmanItem> postmanControllerItems(PsiElement element, String contextPath) {
        if (!isTargetController(element)) {
            return Collections.emptyList();
        }
        List<String> controllerPaths = controllerPaths(element);
        List<? extends PsiElement> methods = controllerMethods(element);

        List<PostmanItem> res = new ArrayList<>();
        for (String controllerPath : controllerPaths) {
            for (PsiElement method : methods) {
                List<PostmanItem> postmanItems = postmanMethodItems(method, contextPath, controllerPath);
                if (Objects.nonNull(postmanItems) && !postmanItems.isEmpty()) {
                    res.addAll(postmanItems);
                }
            }
        }
        return res;
    }

    @Override
    public final HttpLineMarkerInfo createLineMarkerInfo(PsiElement element) {
        if (!isTargetMethod(element)) {
            return null;
        }
        PsiMethod method = (PsiMethod) element;
        return isTargetController(method.getParent()) && Objects.nonNull(method.getNameIdentifier()) ?
                new HttpLineMarkerInfo(method.getNameIdentifier()) : null;
    }

    @Override
    public final String apiPath(PsiElement element, Project project) {
        if (!isTargetMethod(element)) {
            return null;
        }
        PsiMethod method = (PsiMethod) element;
        PsiClass psiClass = method.getContainingClass();
        if (psiClass == null) {
            return null;
        }

        Module module = ModuleUtilCore.findModuleForPsiElement(psiClass);
        return Objects.isNull(module) ? null :
                UrlTool.buildMethodUri(modulePath(module, project), controllerPaths(psiClass).get(0), methodPaths(method).get(0));
    }

    @Override
    public final String getPresentationLocation(PsiElement element) {
        if (!(element instanceof PsiMethod method)) {
            return null;
        }
        PsiClass psiClass = ApplicationManager.getApplication().runReadAction((Computable<PsiClass>) () -> (PsiClass) method.getParent());
        return Optional.ofNullable(psiClass).map(NavigationItem::getName).orElse("") + "#" + method.getName();
    }
}
